﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using PowerGui.AddOn.Interfaces;
using Quest.PowerGUI;
using Quest.PowerGUI.Engine.Shared;
using Quest.PowerGUI.Engine.Shell.Host;
using Quest.PowerGUI.ScriptEditor;
using Quest.PowerGUI.ScriptEditor.Shared;
using Quest.PowerGUI.ScriptEditor.Shared.Debugger;
using Quest.PowerGUI.UI.WinForm.Controls;

namespace PowerGui.AddOn.Impl
{
    [Export(typeof(IConsole))]
    public class InputOutputWindowWrapper : Input_Output_Window, IConsole
    {
        private PowerShellDebugger _debugger;

        [ImportingConstructor]
        public InputOutputWindowWrapper([Import]IPowerGui powerGui, [Import]ConfigLoader configLoader, [Import]PowerShellDebugger debugger)
        {
            configLoader.LoadSettings();

            DebuggerManager.PSHost.SetOutput(this);

            Model.OutputRequested += Model_OutputRequested;
            Model.ResizeOutput += input_Output_Window1_ResizeOutput;

            debugger.Debugger.HostCallBack += EventHostCallback;
            debugger.Debugger.ScriptCompleted += Debugger_ScriptCompleted;

            Font = EditorSettings.Instance.Font;

            _debugger = debugger;

            var syntaxFile = System.IO.Path.Combine(powerGui.InstallPath, "PowerShellSyntax.xml");
            SyntaxEditor.Document.LoadLanguageFromXml(syntaxFile, 0);

            SetOutputWindowLocation();
            Initialize();

            DebugBackgroundColor = Color.Black;
            DebugBackgroundColor = Color.Red;

            BackColor = Color.Black;
            ForeColor = Color.Wheat;
        }

        public UserControl Control
        {
            get { return this; }
        }

        private void SetOutputWindowLocation()
        {
            bool bSuccess;
            string prompt = _debugger.Debugger.GetPrompt(_debugger.Debugger.Shell.Runspace, out bSuccess);
            if (bSuccess && !this.IsDisposed)
            {
                Init(prompt);
            }
        }

        private void Debugger_ScriptCompleted(object sender, EventArgs e)
        {
            SetOutputWindowLocation();
        }


        private void input_Output_Window1_ResizeOutput(object sender, TypedEventArgs<int> e)
        {
            if (e.Value > 1)
            {
                DebuggerManager.PSHost.UI.RawUI.BufferSize = new System.Management.Automation.Host.Size(e.Value, DebuggerManager.PSHost.UI.RawUI.BufferSize.Height);
            }
        }


        private void EventHostCallback(object sender, HostCallbackParameter e)
        {
            HostCallback(e);
        }

        void HostCallback(HostCallbackParameter param)
        {

            if (InvokeRequired)
            {
                Invoke(new MonadHostCallback(HostCallback), new object[] { param });
            }
            else
            {
                switch (param.Type)
                {
                    case HostCallbackParameterTypeEnum.Write:
                        HostWriteCallback((HostWriteCallbackParameter) param);
                        return;
                    case HostCallbackParameterTypeEnum.ClearOutput:
                        CleanOutputWindow();
                        return;
                    case HostCallbackParameterTypeEnum.Prompt:
                        this.ParameterTypeCallback((HostPromtParameter)param);
                        return;
                    case HostCallbackParameterTypeEnum.PromptForCredential:
                        this.PromptForCredentialCallback((HostPromptForCredentialParameter)param);
                        return;
                    case HostCallbackParameterTypeEnum.PromptForChoice:
                        this.ChoiceTypeCallback((HostPromtForChoiceParameter)param);
                        return;


                }
            }
        }

        private void HostWriteCallback(HostWriteCallbackParameter param)
        {
            if (param == null)
            {
                throw new ArgumentNullException();
            }
            switch (param.WriteType)
            {
                case HostWriteTypeEnum.Write:
                case HostWriteTypeEnum.WriteLine:
                    Write(param.Value, OutputViewType.Simple);
                    return;

                case HostWriteTypeEnum.WriteWithParams:
                case HostWriteTypeEnum.WriteLineWithParams:
                    WriteWithParam(param);
                    return;

                case HostWriteTypeEnum.WriteDebugLine:
                    Write("Debug: " + param.Value, OutputViewType.Debug);
                    return;

                case HostWriteTypeEnum.WriteErrorLine:
                    Write(param.Value, OutputViewType.Error);
                    return;

                case HostWriteTypeEnum.WriteVerboseLine:
                    Write(string.Format("Verbose: {0}", param.Value), OutputViewType.Verbose);
                    return;

                case HostWriteTypeEnum.WriteWarningLine:
                    Write("Warning" + param.Value, OutputViewType.Warning);
                    return;

                case HostWriteTypeEnum.WriteProgress:
                    //WriteProgressForm.Process(param.Record, null);
                    return;
            }
        }

        private void Write(string text, OutputViewType outputType)
        {
            AppendText(text, outputType);
        }

        private void WriteWithParam(HostWriteCallbackParameter param)
        {
            if (!base.IsDisposed)
            {
                if (base.InvokeRequired)
                {
                    Action<HostWriteCallbackParameter> method = this.WriteWithParam;
                    base.Invoke(method, new object[] { param });
                }
                else
                {
                    this.AppendText(param.Value, param.ForegroundColor, param.BackgroundColor);
                }
            }
        }


        private void ParameterTypeCallback(HostPromtParameter p)
        {
            List<MonadCommandParameterInfo> list = new List<MonadCommandParameterInfo>();
            foreach (MonadFieldDescription description in p.Descriptions)
            {
                if (description.DefaultValue != null)
                {
                    description.DefaultValue.ToString();
                }
                MonadCommandParameterInfo item = new MonadCommandParameterInfo(description.Name, description.ParameterTypeFullName, description.HelpMessage);
                MonadCommandParameter parameter = MonadCommandParameter.CreateFromPSObject(description.Name, description.ParameterTypeFullName, description.GetDefaultValue());
                item.ParamValue = parameter;
                list.Add(item);
            }
            p.Result = this.ShowParamDialog(p.Caption, p.Message, list.ToArray(), p.CmdLet);
            
        }

        private Dictionary<string, object> ShowParamDialog(string caption, string description, MonadCommandParameterInfo[] parameters, string cmdlet)
        {
            MonadCommandInfo info = new MonadCommandInfo(true, cmdlet, null, null, parameters);
            using (ParametersPromptForm form = new ParametersPromptForm(info, caption, description))
            {
                if (form.ShowDialog() == DialogResult.OK)
                {
                    return form.GetResult2();
                }
                MonadHostCallbackException.Throw();
            }
            return null;
        }

        private void PromptForCredentialCallback(HostPromptForCredentialParameter param)
        {
            string userName = param.UserName;
            string password = null;
            CredentialPromptController.Prompt(param.Caption, param.Message, param.TargetName, param.AllowedCredentialTypes, param.Options, ref userName, out password);
            param.UserName = userName;
            param.Password = password;
        }

        private void ChoiceTypeCallback(HostPromtForChoiceParameter p)
        {
            using (PromptForChoice choice = new PromptForChoice(p.Caption, p.Message, p.Descriptions, p.DefaultChoice))
            {
                if (choice.ShowDialog() == DialogResult.OK)
                {
                    p.Result = choice.GetResult();
                }
                else
                {
                    MonadHostCallbackException.Throw();
                }
            }
        }

        void Model_OutputRequested(object sender, string infoRequested)
        {
            _debugger.Debugger.GetCurrentMonadShell(RunScriptMode.ExecuteCommandLine,
                                                             () =>
                                                             ConfigUtils.GetLoadedSnapins(
                                                                 SettingsManager.ConsoleSettings.Settings));


            Model.InputEnabled = true;

            ScriptProvider p = new ScriptProvider
                                   {
                                       Script = infoRequested
                                   };

            _debugger.Debugger.Execute(p, RunScriptMode.ExecuteCommandLine);
        }

        public PSSyntaxEditorControl SyntaxEditor
        {
            get
            {
                var field = typeof(Input_Output_Window).GetField("edbCommand",
                                   BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy |
                                   BindingFlags.GetField);

                return (PSSyntaxEditorControl)field.GetValue(this);
            }
        }

        public InputOutputViewModel Model
        {
            get
            {
                return (InputOutputViewModel)typeof (Input_Output_Window).GetProperty("Model",
                                                         BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic |
                                                         BindingFlags.GetProperty).GetValue(this, null);
            }
        }
    }
}
